/*
 * HackSQL
 * Copyright (C) 2008-2009
 *
 * Authors: Felipe Nascimento Silva Pena
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * $Id: hs_catalog.c 52 2009-03-30 01:00:01Z heuripedes $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <hacksql/hacksql.h>
#include <hacksql/hs_types.h>
#include <hacksql/hs_hashtable.h>
#include <hacksql/hs_catalog.h>

#define HS_NUM_SCHEMAS_DEFAULT 2
#define HS_NUM_ROLES_DEFAULT   2
#define HS_NUM_USERS_DEFAULT   2
#define HS_NUM_TABLES_DEFAULT  2

#define HS_CATALOG_FILE "/home/felipe/catalog.hs"

hs_catalog *catalog;

/*
 * Returns the field type name
 */
static const hs_string hs_type_name(hs_field_type type)
{
	switch (type) {
		case HS_FIELD_BLOB:
			return "blob";
		case HS_FIELD_BOOL:
			return "bool";
		case HS_FIELD_CHAR:
			return "char";
		case HS_FIELD_VARCHAR:
			return "varchar";
		case HS_FIELD_INTEGER:
			return "integer";
		default:
			return "unknown";
	}
}

static const hs_string hs_constraint_type_name(hs_constraint_type type)
{
	switch (type) {
		case HS_COLUMN_PK:
			return "primary key";
		case HS_COLUMN_FK:
			return "foreing key";
		case HS_COLUMN_UNIQUE:
			return "unique";
		default:
			return "unknown";
	}
}

hs_schema *hs_create_schema(char *name, hs_table **tables, hs_uint num_tables)
{
	hs_schema *schema;
	
	schema = malloc(sizeof(hs_schema));
	schema->name_len = strlen(name);
	schema->name = strdup(name);
	schema->num_tables = num_tables;
	schema->tables = tables;
	
	return schema;
}

void hs_create_field(hs_field **field, char *name, hs_field_type type)
{
	(*field)->name_len = strlen(name);
	(*field)->name = strdup(name);
	(*field)->type = type;
}

void hs_create_table(hs_table **table, char *name, hs_field **fields, hs_uint num_fields, hs_constraint **constraints, 
	hs_uint num_constraints, char *comment)
{
	(*table)->name_len = strlen(name);
	(*table)->name = strdup(name);
	(*table)->comment_len = (comment == NULL ? 0 : strlen(comment));
	(*table)->comment = comment;
	(*table)->num_fields = num_fields;
	(*table)->fields = fields;
	(*table)->num_constraints = num_constraints;
	(*table)->constraints = constraints;
	(*table)->version = HACKSQL_NUM_VERSION;
	(*table)->create_time = (*table)->update_time = time(NULL);
}

hs_user *hs_create_user(char *name, hs_uint privileges)
{
	hs_user *user;
	
	user = malloc(sizeof(hs_user));
	user->name_len = strlen(name);
	user->name = strdup(name);
	user->privileges = privileges;
	
	return user;
}

void hs_create_constraint(hs_constraint **constraint, hs_field **field, hs_uint num_fields, char *name, hs_constraint_type type)
{
	(*constraint)->name_len = strlen(name);
	(*constraint)->name = strdup(name);
	(*constraint)->type = type;
	(*constraint)->num_fields = num_fields;
	(*constraint)->fields = field;
}

hs_role *hs_create_role(char *name, hs_user **users, hs_uint num_users)
{
	hs_role *role;
	
	role = (hs_role *) malloc(sizeof(hs_role));
	role->name_len = strlen(name);
	role->name = strdup(name);
	role->num_users = num_users;
	role->users = users;
	
	return role;	
}

void hs_initialize_catalog(hs_catalog **catalog)
{
	hs_table **tables;
	hs_field **fields;
	hs_constraint **constraints;
	
	/* Creating a field */
	fields = (hs_field **) calloc(2, sizeof(hs_field *));
	*fields = (hs_field *) malloc(sizeof(hs_field));
	*(fields+1) = (hs_field *) malloc(sizeof(hs_field));
	hs_create_field(fields, "foo", HS_FIELD_VARCHAR);
	hs_create_field(fields+1, "foo2", HS_FIELD_BOOL);
	
	/* Creating constraint */
	constraints = (hs_constraint **) calloc(1, sizeof(hs_constraint *));
	*constraints = (hs_constraint *) malloc(sizeof(hs_constraint));
	hs_create_constraint(constraints, fields+1, 1, "TEST_UK", HS_COLUMN_UNIQUE);
	
	
	/* Creating a table */
	tables = (hs_table **) calloc(1, sizeof(hs_table *));
	*tables = (hs_table *) malloc(sizeof(hs_table));	
	hs_create_table(tables,  "table_teste", fields, 2, constraints, 1, NULL);

	/* Creating a schema */
	(*catalog)->num_schemas = 1;
	(*catalog)->schemas = (hs_schema **) calloc(HS_NUM_SCHEMAS_DEFAULT, sizeof(hs_schema *));
	*(*catalog)->schemas = hs_create_schema("schema_teste", tables, 1);
	
	/* Creating an user */
	(*catalog)->num_users = 1;
	(*catalog)->users = (hs_user **) calloc(HS_NUM_USERS_DEFAULT, sizeof(hs_user *));
	*(*catalog)->users = hs_create_user("felipe", HS_PRIV_ALL);
	
	/* Creating a role */
	(*catalog)->num_roles = 1;
	(*catalog)->roles = (hs_role **) calloc(HS_NUM_ROLES_DEFAULT, sizeof(hs_role *));
	*(*catalog)->roles = hs_create_role("ADMIN", (*catalog)->users, 1);
}

hs_bool hs_catalog_save(hs_catalog **catalog)
{
	FILE *fp;
	hs_uint i, j, k, l;
	hs_table *table_p;
	hs_field *field_p;
	hs_constraint *constraint_p;
	hs_role **roles_pp;
	hs_user **users_pp;
	
	if ((fp = fopen(HS_CATALOG_FILE, "wb")) == NULL) {
		return FAILURE;
	}
	
	fwrite(&(*catalog)->num_schemas, sizeof(hs_uint), 1, fp);
	
	for (i = 0; i < (*catalog)->num_schemas; i++) {
		fwrite(&(*catalog)->schemas[i]->name_len, sizeof(hs_uint), 1, fp);
		fwrite((*catalog)->schemas[i]->name, sizeof(char), (*catalog)->schemas[i]->name_len+1, fp);
		fwrite(&(*catalog)->schemas[i]->num_tables, sizeof(hs_uint), 1, fp);
		
		for (j = 0; j < (*catalog)->schemas[i]->num_tables; j++) {
			table_p = (*catalog)->schemas[i]->tables[j];
			
			fwrite(&table_p->name_len, sizeof(hs_uint), 1, fp);
			fwrite(table_p->name, sizeof(char), table_p->name_len+1, fp);
			
			fwrite(&table_p->comment_len, sizeof(hs_uint), 1, fp);
			fwrite(&table_p->comment, sizeof(char), table_p->comment_len+1, fp);
			fwrite(&table_p->num_fields, sizeof(hs_uint), 1, fp);
			
			for (k = 0; k < table_p->num_fields; k++) {
				field_p = table_p->fields[k];
				
				fwrite(&field_p->name_len, sizeof(hs_uint), 1, fp);
				fwrite(field_p->name, sizeof(char), field_p->name_len+1, fp);
				fwrite(&field_p->type, sizeof(hs_field_type), 1, fp);
			}
			
			fwrite(&table_p->num_constraints, sizeof(hs_uint), 1, fp);
			
			for (l = 0; l < table_p->num_constraints; l++) {
				hs_field **constr_field;
				
				constraint_p = table_p->constraints[l];
				
				fwrite(&constraint_p->name_len, sizeof(hs_uint), 1, fp);
				fwrite(constraint_p->name, sizeof(char), constraint_p->name_len+1, fp);
				fwrite(&constraint_p->num_fields, sizeof(hs_uint), 1, fp);				
				constr_field = constraint_p->fields;
				
				while (constr_field && *constr_field) {
					fwrite(&(*constr_field)->name_len, sizeof(hs_uint), 1, fp);
					fwrite((*constr_field)->name, sizeof(char), (*constr_field)->name_len+1, fp);
					fwrite(&(*constr_field)->type, sizeof(hs_field_type), 1, fp);
					constr_field++;
				}
				
				fwrite(&constraint_p->type, sizeof(hs_constraint_type), 1, fp);
			}
			
			fwrite(&table_p->version, sizeof(hs_uint), 1, fp);
			fwrite(&table_p->create_time, sizeof(time_t), 1, fp);
			fwrite(&table_p->update_time, sizeof(time_t), 1, fp);
		}
	}
	
	fwrite(&(*catalog)->num_roles, sizeof(hs_uint), 1, fp);
	
	roles_pp = (*catalog)->roles;
	
	while (roles_pp && *roles_pp) {
		fwrite(&(*roles_pp)->name_len, sizeof(hs_uint), 1, fp);
		fwrite((*roles_pp)->name, sizeof(char), (*roles_pp)->name_len+1, fp);
		fwrite(&(*roles_pp)->num_users, sizeof(hs_uint), 1, fp);
		
		users_pp = (*roles_pp)->users;
		while (users_pp && *users_pp) {
			fwrite(&(*users_pp)->name_len, sizeof(hs_uint), 1, fp);
			fwrite((*users_pp)->name, sizeof(char), (*users_pp)->name_len+1, fp);
			fwrite(&(*users_pp)->privileges, sizeof(hs_privilege), 1, fp);
			users_pp++;
		}
		roles_pp++;
	}

	fwrite(&(*catalog)->num_users, sizeof(hs_uint), 1, fp);
	
	users_pp = (*catalog)->users;
	while (users_pp && *users_pp) {
		fwrite(&(*users_pp)->name_len, sizeof(hs_uint), 1, fp);
		fwrite((*users_pp)->name, sizeof(char), (*users_pp)->name_len+1, fp);
		fwrite(&(*users_pp)->privileges, sizeof(hs_privilege), 1, fp);
		users_pp++;
	}
	
	fclose(fp);
	
	return SUCCESS;
}

hs_bool hs_catalog_read(hs_catalog **catalog)
{
	FILE *fp;
	hs_schema *schema_p;
	hs_field *field_p;
	hs_role *roles_p;
	hs_user *users_p;
	hs_constraint *constraint_p;
	hs_table *table_p;
	hs_uint i, j, k, l, m;

	if ((fp = fopen(HS_CATALOG_FILE, "rb")) == NULL) {
		return FAILURE;
	}

	fread(&(*catalog)->num_schemas, sizeof(hs_uint), 1, fp);
	(*catalog)->schemas = (hs_schema **) calloc((*catalog)->num_schemas, sizeof(hs_schema *));
	
	for (i = 0; i < (*catalog)->num_schemas; i++) {
		(*catalog)->schemas[i] = (hs_schema *) malloc(sizeof(hs_schema));
				
		schema_p = (*catalog)->schemas[i];
		
		fread(&schema_p->name_len, sizeof(hs_uint), 1, fp);
		schema_p->name = calloc(schema_p->name_len+1, 1);
		fread(schema_p->name, sizeof(char), schema_p->name_len+1, fp);
		fread(&schema_p->num_tables, sizeof(hs_uint), 1, fp);
		
		schema_p->tables = (hs_table **) calloc(schema_p->num_tables, sizeof(hs_table *));
		
		for (j = 0; j < schema_p->num_tables; j++) {
			schema_p->tables[j] = (hs_table *) malloc(sizeof(hs_table));
			
			table_p = schema_p->tables[j];
			
			fread(&table_p->name_len, sizeof(hs_uint), 1, fp);
			table_p->name = (char *) calloc(table_p->name_len+1, sizeof(char));
			fread(table_p->name, sizeof(char), table_p->name_len+1, fp);
			fread(&table_p->comment_len, sizeof(hs_uint), 1, fp);
			fread(&table_p->comment, sizeof(char), table_p->comment_len+1, fp);
			fread(&table_p->num_fields, sizeof(hs_uint), 1, fp);
			
			table_p->fields = (hs_field **) calloc(table_p->num_fields, sizeof(hs_field *));
			
			for (k = 0; k < table_p->num_fields; k++) {
				table_p->fields[k] = (hs_field *) malloc(sizeof(hs_field));
				
				field_p = table_p->fields[k];
				
				fread(&field_p->name_len, sizeof(hs_uint), 1, fp);
				field_p->name = (char *) calloc(field_p->name_len+1, sizeof(char));
				fread(field_p->name, sizeof(char), field_p->name_len+1, fp);
				fread(&field_p->type, sizeof(hs_field_type), 1, fp);
			}
			
			fread(&table_p->num_constraints, sizeof(hs_uint), 1, fp);
			table_p->constraints = (hs_constraint **) calloc(table_p->num_constraints, sizeof(hs_constraint *));
			
			for (l = 0; l < table_p->num_constraints; l++) {
				table_p->constraints[l] = (hs_constraint *) malloc(sizeof(hs_constraint));
				
				constraint_p = table_p->constraints[l];
				
				fread(&constraint_p->name_len, sizeof(hs_uint), 1, fp);
				constraint_p->name = (char *) calloc(constraint_p->name_len+1, sizeof(char));
				fread(constraint_p->name, sizeof(char), constraint_p->name_len+1, fp);
				fread(&constraint_p->num_fields, sizeof(hs_uint), 1, fp);
				
				constraint_p->fields = (hs_field **) calloc(constraint_p->num_fields, sizeof(hs_field *));
				
				for (m = 0; m < constraint_p->num_fields; m++) {
					constraint_p->fields[m] = (hs_field *) malloc(sizeof(hs_field));					
					
					field_p = constraint_p->fields[m];
					
					fread(&field_p->name_len, sizeof(hs_uint), 1, fp);
					field_p->name = (char *) calloc(field_p->name_len+1, sizeof(char));
					fread(field_p->name, sizeof(char), field_p->name_len+1, fp);
					fread(&field_p->type, sizeof(hs_field_type), 1, fp);
				}
				
				fread(&constraint_p->type, sizeof(hs_constraint_type), 1, fp);
			}
			
			fread(&table_p->version, sizeof(hs_uint), 1, fp);
			fread(&table_p->create_time, sizeof(time_t), 1, fp);
			fread(&table_p->update_time, sizeof(time_t), 1, fp);
		}
	}
	
	/* Roles */
	fread(&(*catalog)->num_roles, sizeof(hs_uint), 1, fp);
	
	(*catalog)->roles = (hs_role **) calloc((*catalog)->num_roles, sizeof(hs_role *));
	for (i = 0; i < (*catalog)->num_roles; i++) {
		(*catalog)->roles[i] = (hs_role *) malloc(sizeof(hs_role));
		roles_p = (*catalog)->roles[i];
		
		fread(&roles_p->name_len, sizeof(hs_uint), 1, fp);
		roles_p->name = (char *) calloc(roles_p->name_len, sizeof(char));
		fread(roles_p->name, sizeof(char), roles_p->name_len+1, fp);			
		fread(&roles_p->num_users, sizeof(hs_uint), 1, fp);
		
		roles_p->users = (hs_user **) calloc(roles_p->num_users, sizeof(hs_user *));

		for (j = 0; j < roles_p->num_users; j++) {
			roles_p->users[j] = (hs_user *) malloc(sizeof(hs_user));
			users_p = roles_p->users[j];
			
			fread(&users_p->name_len, sizeof(hs_uint), 1, fp);
			users_p->name = (char *) calloc(users_p->name_len+1, sizeof(char));
			fread(users_p->name, sizeof(char), users_p->name_len+1, fp);
			fread(&users_p->privileges, sizeof(hs_privilege), 1, fp);
		}
	}

	/* Users */
	fread(&(*catalog)->num_users, sizeof(hs_uint), 1, fp);
	(*catalog)->users = (hs_user **) calloc((*catalog)->num_users, sizeof(hs_user *));
	for (i = 0; i < (*catalog)->num_users; i++) {
		(*catalog)->users[i] = (hs_user *) malloc(sizeof(hs_user));
		users_p = (*catalog)->users[i];
		
		fread(&users_p->name_len, sizeof(hs_uint), 1, fp);
		users_p->name = (char *) calloc(users_p->name_len+1, sizeof(char));
		fread(users_p->name, sizeof(char), users_p->name_len+1, fp);
		fread(&users_p->privileges, sizeof(hs_privilege), 1, fp);
	}
	
	fclose(fp);
	
	return SUCCESS;
}

void hs_catalog_dump(hs_catalog **catalog)
{
	hs_uint i;
	hs_schema *schemas_p;
	hs_table **tables_pp;
	hs_field **fields_pp;
	hs_role **roles_pp;
	hs_user **users_pp;
	hs_constraint **constraints_pp;

	printf("Schemas [%d] {\n", (*catalog)->num_schemas);
	for (i = 0; i < (*catalog)->num_schemas; i++) {
		schemas_p = (*catalog)->schemas[i];
				
		printf("  - %s\n", schemas_p->name);
		printf("  Tables [%d] {\n", schemas_p->num_tables);
		
		if (schemas_p->tables) {			
			tables_pp = schemas_p->tables;			
			
			while (*tables_pp) {
				fields_pp = (*tables_pp)->fields;
				constraints_pp = (*tables_pp)->constraints;
				
				printf("    - %s (version: %d; comment: '%s')\n", (*tables_pp)->name, (*tables_pp)->version, HS_SECURE_STR((*tables_pp)->comment));
				printf("      Created: %d ; Updated: %d\n", (hs_uint) (*tables_pp)->create_time, (hs_uint) (*tables_pp)->update_time);
				printf("    Fields [%d] {\n", (*tables_pp)->num_fields);
				while (*fields_pp) {
					printf("      - %s (%s)\n", (*fields_pp)->name, hs_type_name((*fields_pp)->type));
					fields_pp++;
				}
				printf("    }\n");
				
				printf("    Constraints [%d] {\n", (*tables_pp)->num_constraints);
				while (*constraints_pp) {
					fields_pp = (*constraints_pp)->fields;
					
					printf("      %s (%s) (\n       ", (*constraints_pp)->name, hs_constraint_type_name((*constraints_pp)->type));
					
					while (*fields_pp) {
						printf("%s,\n", (*fields_pp)->name);
						fields_pp++;
					}
					printf("      )\n");			
					constraints_pp++;
				}
				printf("    }\n");
				tables_pp++;
			}
		}		
		printf("  }\n");
	}
	printf("}\n");
	
	printf("Roles [%d] {\n", (*catalog)->num_roles);
	roles_pp = (*catalog)->roles;
	while (*roles_pp) {
		users_pp = (*roles_pp)->users;
		
		printf("  - %s\n", (*roles_pp)->name);
		
		printf("  Users [%d] {\n", (*roles_pp)->num_users);
		while (*users_pp) {
			printf("     - %s\n", (*users_pp)->name);
			users_pp++;
		}
		printf("  }\n");
		roles_pp++;
	}
	printf("}\n");
	
	printf("Users [%d] {\n", (*catalog)->num_users);
	users_pp = (*catalog)->users;
	while (*users_pp) {
		printf("  - %s\n", (*users_pp)->name);
		users_pp++;
	}
	printf("}\n");
}

void hs_catalog_init(void)
{
	hs_catalog *catalog2;
	hs_table *foo;
	
	catalog = (hs_catalog *) malloc(sizeof(hs_catalog));
	catalog2 = (hs_catalog *) malloc(sizeof(hs_catalog));
	
#if 1
	hs_initialize_catalog(&catalog2);

	if (hs_catalog_save(&catalog2) == FAILURE) {
		printf("Falha ao salvar arquivo!\n");
	}
#endif
	
	hs_catalog_read(&catalog);
	
	/*hs_catalog_dump(&catalog);*/
}

/*
int main(int argc, char **argv)
{
	hs_catalog_init();

	return 0;	
}
*/
